package gt

import (
	"math"
	"strconv"

	"gitee.com/dark.H/gs"
	"github.com/nsf/termbox-go"
)

type ListState[T any] struct {
	Offset    int
	Cursor    int
	SearchKey gs.Str
}

type ListItemsPanel[T any] struct {
	*PanelHandler
	ShowNum   bool
	AuthSize  bool
	Cursor    int
	MaxHeight int
	Items     gs.List[T]
	SearchKey gs.Str

	// selected  T
	savedKeys gs.Str

	SelectedFmt func(selected gs.Str) gs.Str
}

func NewListItemsPanel[T any](items gs.List[T]) *ListItemsPanel[T] {
	maxh, _ := gs.GetWindowsSize()
	lpt := &ListItemsPanel[T]{
		PanelHandler: NewPanelHandler(),
		Items:        items,
		ShowNum:      true,
		AuthSize:     true,
		MaxHeight:    maxh,
		SelectedFmt: func(selected gs.Str) gs.Str {
			return gs.S(selected).ANSISelected()
		},
	}
	if lpt.AuthSize {
		maxWidth := lpt.GetMaxWidth()
		authHeight := lpt.GetMaxHeigh()
		if items.Count()+2 < authHeight {
			authHeight = items.Count() + 2
		}

		lpt.SetSize(maxWidth, authHeight)
	}

	lpt.DefaultKeys()
	lpt.SetDrawFunc(lpt.draw)
	// lpt.SetSelectLine(1)
	return lpt
}

func (lph *ListItemsPanel[T]) GetState() ListState[T] {
	_, off := lph.Pos()
	return ListState[T]{
		Offset:    off,
		Cursor:    lph.Cursor,
		SearchKey: lph.SearchKey,
	}
}

func (lph *ListItemsPanel[T]) LoadState(state ListState[T]) *ListItemsPanel[T] {
	lph.SetPosY(state.Offset)
	lph.Cursor = state.Cursor
	lph.SearchKey = state.SearchKey
	// lph.selected = state.Selected
	return lph
}

func (lph *ListItemsPanel[T]) ResetState() *ListItemsPanel[T] {
	lph.SetPosY(0)
	lph.Cursor = 0
	// lph.selected = lph.Items[0]
	lph.SearchKey = ""
	return lph
}

func (lph *ListItemsPanel[T]) SetMaxHeight(h int) *ListItemsPanel[T] {
	lph.MaxHeight = h
	return lph
}

func (lph *ListItemsPanel[T]) SetSelectFmt(s func(selected gs.Str) gs.Str) *ListItemsPanel[T] {
	if s != nil {
		lph.SelectedFmt = s

	}
	return lph
}

func (lph *ListItemsPanel[T]) GetMaxWidth() int {
	c := lph.Width
	lph.GetFilter().Every(func(no int, i T) {
		ci := gs.S(i).Printable(true).Len()
		if ci > c {
			c = ci
		}
	})
	return c
}

func (lph *ListItemsPanel[T]) GetMaxHeigh() int {
	mxheig, _ := gs.GetWindowsSize()
	c := mxheig - lph.LocY()
	// gs.Str("lph.MaxHeight: %d - lph.LocX():%d ").F(lph.MaxHeight, lph.LocY()).ToFile("/tmp/etst.log")
	if c < 0 {
		c = 30
		if c > lph.MaxHeight {
			c = lph.MaxHeight
		}
	}
	// if lph.loc.ShowBorder {
	// 	c -= 2
	// }

	ci := lph.GetFilter().Count()
	if lph.loc.ShowBorder {
		ci += 2
	}
	if ci > c {
		return c
	}
	return ci
}

func (lph *ListItemsPanel[T]) OnSelected(call func(item T)) {
	lph.OnChanged(func(p *PanelHandler) bool {
		call(lph.Selected())
		return false
	})
}

func (lph *ListItemsPanel[T]) DefaultKeys() {
	lph.OnkeyCh('j', func(parentPanel *PanelHandler) bool {

		if lph.savedKeys == "" {
			lph.savedKeys = "1"
		}
		ecou, err := strconv.Atoi(lph.savedKeys.Str())
		if err != nil {
			ecou = 1
		}
		for i := 0; i < ecou; i++ {
			lph.Down()
		}

		lph.savedKeys = ""

		return false
	}).Onkey(termbox.KeyArrowDown, func(parentPanel *PanelHandler) bool {
		if lph.savedKeys == "" {
			lph.savedKeys = "1"
		}
		ecou, err := strconv.Atoi(lph.savedKeys.Str())
		if err != nil {
			ecou = 1
		}
		for i := 0; i < ecou; i++ {
			lph.Down()
		}

		lph.savedKeys = ""

		return false
	}).OnkeyCh('k', func(parentPanel *PanelHandler) bool {
		if lph.savedKeys == "" {
			lph.savedKeys = "1"
		}
		ecou, err := strconv.Atoi(lph.savedKeys.Str())
		if err != nil {
			ecou = 1
		}
		for i := 0; i < ecou; i++ {
			lph.Up()
		}

		lph.savedKeys = ""

		return false
	}).Onkey(termbox.KeyArrowUp, func(parentPanel *PanelHandler) bool {
		if lph.savedKeys == "" {
			lph.savedKeys = "1"
		}
		ecou, err := strconv.Atoi(lph.savedKeys.Str())
		if err != nil {
			ecou = 1
		}
		for i := 0; i < ecou; i++ {
			lph.Up()
		}

		lph.savedKeys = ""
		return false
	}).OnkeyCh('/', func(parentPanel *PanelHandler) bool {
		x, y, h, w := lph.LocSize()
		if lph.loc.ShowBorder {
			lph.Switch(func(parentPanel *PanelHandler) bool {
				locx, locy := parentPanel.Loc()
				searchKey := QuickInput(locx+1, locy, gs.Str("Search:"))
				lph.SearchKey = searchKey
				lph.Cursor = 0
				lph.SetPosY(0)
				return true
			})

		} else {
			lph.Switch(func(parentPanel *PanelHandler) bool {
				locx, locy := parentPanel.Loc()
				searchKey := QuickInput(locx, locy, gs.Str("Search:"))
				lph.SearchKey = searchKey
				lph.Cursor = 0
				lph.SetPosY(0)
				return true
			})
		}
		lph.ClearArea(x, y, w, h, '.')
		return false
	}).OnkeyDel(func(parentPanel *PanelHandler) bool {
		if lph.SearchKey.Len() > 0 {
			lph.SearchKey = lph.SearchKey[:lph.SearchKey.Len()-1]
		}

		return false
	}).OnClick(func(x, y int, panel *PanelHandler) bool {
		if panel.IsInMe(x, y) {
			row := y - panel.loc.Y
			_, offsetY := panel.Pos()
			lph.Cursor = row + offsetY
		}
		return false
	}).OnClickScrollDown(func(x, y int, panel *PanelHandler) bool {
		if panel.IsInMe(x, y) {
			lph.Down()
		}
		return false
	}).OnClickScrollUp(func(x, y int, panel *PanelHandler) bool {
		if panel.IsInMe(x, y) {
			lph.Up()
		}
		return false
	})
}

func (lph *ListItemsPanel[T]) GetFilter() (filtered gs.List[T]) {
	if lph.SearchKey == "" {
		return lph.Items
	}
	lph.Items.Every(func(no int, i T) {
		if gs.S(i).In(lph.SearchKey) {
			filtered = append(filtered, i)
		}
	})
	return
}

func (lph *ListItemsPanel[T]) Down() *ListItemsPanel[T] {
	lph.Cursor += 1
	fs := lph.GetFilter()
	if lph.Cursor == fs.Count() {
		lph.Cursor = 0
		lph.SetPosY(0)
	}

	_, offsetHeight := lph.Pos()
	_, WindowHeight := lph.InnerSize()
	if lph.Cursor >= WindowHeight+offsetHeight {
		lph.SetPosY(offsetHeight + 1)
	}

	return lph
}

func (lph *ListItemsPanel[T]) Up() *ListItemsPanel[T] {
	lph.Cursor -= 1

	_, Window := lph.InnerSize()
	fs := lph.GetFilter()
	if lph.Cursor < 0 {
		lph.Cursor = fs.Count() + lph.Cursor
		lph.SetPosY(lph.Cursor - Window + 1)
	}
	_, offsetHeight := lph.Pos()
	if lph.Cursor < offsetHeight {

		lph.SetPosY(offsetHeight - 1)
	}

	return lph
}

func (lph *ListItemsPanel[T]) Selected() T {
	fs := lph.GetFilter()
	return fs[lph.Cursor]
}

func (lph *ListItemsPanel[T]) draw() {
	if lph.Items.Count() == 0 {
		return
	}

	fs := lph.GetFilter()
	if lph.AuthSize {
		// maxWidth := lph.GetMaxWidth()
		// authHeight := lph.GetMaxHeigh()
		// lph.SetSize(maxWidth, authHeight)
		maxWidth := lph.GetMaxWidth()
		authHeight := lph.GetMaxHeigh()
		if fs.Count()+2 < authHeight {
			authHeight = fs.Count() + 2

		}
		lph.SetSize(maxWidth, authHeight)

		// lph.LogInfo("/tmp/ss.txt")
	}
	if lph.SearchKey.Len() > 0 {
		// title := lph.GetTitle()
		title := lph.GetTitle().Split("[Search]:", 2)[0]
		lph.SetTitle(title + "[Search]:" + lph.SearchKey)
	} else {
		if lph.loc.TItle.In("[Search]:") && lph.SearchKey.Len() == 0 {
			title := lph.GetTitle().Split("[Search]:", 2)[0]
			lph.SetTitle(title)
		}
	}
	e := fs.Count()
	// lph.LogInfo("/tmp/ss.txt")

	c10 := int(math.Log10(float64(e))) + 1
	cfmt := gs.Str("%%%dd ").F(c10)
	lines := gs.List[gs.Str]{}
	fs.Every(func(no int, i T) {
		pre := ""
		mid := ""
		if lph.ShowNum {
			mid = cfmt.F(no).Color("B").Str()
		}
		if no == lph.Cursor {
			lines = lines.Add(gs.Str(pre+" %s%s").F(mid, lph.SelectedFmt(gs.S(i))))
		} else {
			lines = lines.Add(gs.Str(pre+" %s%s").F(mid, gs.S(i)))
		}
	})
	_, yoff := lph.Pos()
	// gs.Str("%d").F(lph.Cursor - yoff).ToFile("/tmp/ss.txt")
	lph.SetSelectLine(lph.Cursor - yoff)
	// lph.LogInfo("/tmp/ss.txt")
	lph.Write(lines.Join("\n"))

}

func (lph *ListItemsPanel[T]) GetSelectPos(addSelectedWidth ...bool) (x int, y int) {
	x, y = lph.Loc()
	_, offsetHeight := lph.Pos()
	y += lph.Cursor - offsetHeight
	if lph.ShowNum {
		x += 2
	}
	if addSelectedWidth != nil && addSelectedWidth[0] {
		cc := gs.S(lph.Selected).Len()
		if cc > lph.Width {
			cc = lph.Width - 2
		}
		x += cc
	}

	if lph.loc.ShowBorder {
		x += 1
		y += 1
	}
	return
}

/*
args:

	    OnExit(item T)
		OnEnter(item T)
		GetSubItems func (mainSeleted T) (subitems gs.List[T])

这种监听模式会在每次改变Seleted 时候触发一个函数 GetSubItems， 该函数返回子列表，如果选择则会把子列表当作父列表
*/
func (lph *ListItemsPanel[T]) OnFileLike(onEnter func(enterItem T), onExit func(oldState ListState[T]), getSubitems func(mainSeleted T) (subitems gs.List[T]), sublistWidth ...int) *ListItemsPanel[T] {
	subitems := gs.List[T]{}
	olditems := gs.List[T]{}
	var oldState ListState[T]
	lph.OnSelected(func(item T) {
		lph.Switch(func(parentPanel *PanelHandler) bool {
			_, y2 := lph.GetSelectPos(true)

			selected := lph.Selected
			subitems = getSubitems(item)

			winh, win := gs.GetWindowsSize()
			roffset := lph.LocX() + lph.Width
			wi := win - roffset - 1
			if sublistWidth != nil && sublistWidth[0] < wi {
				wi = sublistWidth[0]
			}
			lph.ClearArea(lph.LocX()+lph.Width, lph.LocY(), wi, winh)
			if subitems.Count() > 0 {
				subpanel := NewListItemsPanel(subitems)

				subpanel.SetSizeWidth(wi).SetLoc(lph.LocX()+lph.Width, y2, true).SetBar(lph.loc.ShowBar).SetBorder(lph.loc.ShowBorder)
				subpanel.SetTitle(gs.S(selected))

				subpanel.Draw()
			}

			return true
		})
	})
	lph.OnkeyEnter(func(parentPanel *PanelHandler) bool {

		onEnter(lph.Selected())
		lph.SearchKey = ""
		if subitems.Count() > 0 {
			olditems = lph.Items
			oldState = lph.GetState()
			lph.Items = subitems
			lph.ResetState()

			subitems = getSubitems(lph.Selected())

			_, y2 := lph.GetSelectPos(true)
			_, win := gs.GetWindowsSize()
			roffset := lph.LocX() + lph.Width
			wi := win - roffset - 1
			if sublistWidth != nil && sublistWidth[0] < wi {
				wi = sublistWidth[0]
			}
			lph.ClearAll()
			if subitems.Count() > 0 {
				subpanel := NewListItemsPanel(subitems)

				subpanel.SetSizeWidth(wi).SetLoc(lph.LocX()+lph.Width, y2, true).SetBar(lph.loc.ShowBar).SetBorder(lph.loc.ShowBorder)
				subpanel.SetTitle(gs.S(lph.Selected()))
				// subpanel.ClearArea(lph.LocX()+lph.Width, lph.LocY(), lph.Width, lph.Height)
				subpanel.Draw()
			}
		}
		return false
	})
	lph.OnkeyCh('h', func(parentPanel *PanelHandler) bool {
		onExit(oldState)
		if olditems.Count() > 0 {
			subitems = lph.Items
			lph.Items = olditems

			lph.LoadState(oldState)

			_, y2 := lph.GetSelectPos(true)
			_, win := gs.GetWindowsSize()
			roffset := lph.LocX() + lph.Width
			wi := win - roffset - 1
			if sublistWidth != nil && sublistWidth[0] < wi {
				wi = sublistWidth[0]
			}
			lph.ClearArea(lph.LocX()+lph.Width, lph.LocY(), wi, lph.Height)
			if subitems.Count() > 0 {
				subpanel := NewListItemsPanel(subitems)
				subpanel.SetSizeWidth(wi).SetLoc(lph.LocX()+lph.Width, y2, true).SetBar(lph.loc.ShowBar).SetBorder(lph.loc.ShowBorder)
				subpanel.SetTitle(gs.S(lph.Selected))
				// subpanel.ClearArea(lph.LocX()+lph.Width, lph.LocY(), lph.Width, lph.Height)
				subpanel.Draw()
			}

		}
		return false
	}).OnKeyChSameAs('l', termbox.KeyEnter)
	return lph
}

func QuickListSelect[T any](Items gs.List[T], x, y, width, height int) T {
	t := NewListItemsPanel(Items)
	t.SetLoc(x, y).SetBorder(false).SetSize(width, height).SetTitle("liste choose one")
	t.Onkey(termbox.KeyEnter, func(parentPanel *PanelHandler) bool {
		return true
	})
	t.Focus()
	return t.Selected()
}
